/* context.c - X context management
 *
 * Raster graphics library
 *
 * Copyright (c) 1997-2003 Alfredo K. Kojima
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#ifdef HAVE_LIBXMU
#include <X11/Xmu/StdCmap.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <math.h>

#include "config.h"
#include "wraster.h"
#include "scale.h"
#include "wr_i18n.h"

#ifndef HAVE_FLOAT_MATHFUNC
#define powf(x, y) ((float)pow((double)(x), (double)(y)))
#endif

static Bool bestContext(Display *dpy, int screen_number, RContext *context);

static const RContextAttributes DEFAULT_CONTEXT_ATTRIBS = {
    RC_UseSharedMemory | RC_RenderMode | RC_ColorsPerChannel, /* flags */
    RDitheredRendering,                                       /* render_mode */
    4,                                                        /* colors_per_channel */
    0,
    0,
    0,
    0,
    True, /* use_shared_memory */
    RMitchellFilter,
    RUseStdColormap};

/*
 *
 * Colormap allocation for PseudoColor visuals:
 *
 *
 * switch standardColormap:
 * 	none:
 * 		allocate colors according to colors_per_channel
 *
 * 	best/default:
 * 		if there's a std colormap defined then use it
 *
 * 		else
 * 			create a std colormap and set it
 */

/*
 *----------------------------------------------------------------------
 * allocateStandardPseudoColor
 * 	Creates the internal colormap for PseudoColor, setting the
 * color values according to the supplied standard colormap.
 *
 * Returns: -
 *
 * Side effects: -
 *
 * Notes: -
 *----------------------------------------------------------------------
 */
static Bool allocateStandardPseudoColor(RContext *ctx, XStandardColormap *stdcmap)
{
  int i;

  ctx->ncolors = stdcmap->red_max * stdcmap->red_mult + stdcmap->green_max * stdcmap->green_mult +
                 stdcmap->blue_max * stdcmap->blue_mult + 1;

  if (ctx->ncolors <= 1) {
    RErrorCode = RERR_INTERNAL;
    puts("wraster: bad standard colormap");

    return False;
  }

  ctx->colors = malloc(sizeof(XColor) * ctx->ncolors);
  if (!ctx->colors) {
    RErrorCode = RERR_NOMEMORY;

    return False;
  }

  ctx->pixels = malloc(sizeof(unsigned long) * ctx->ncolors);
  if (!ctx->pixels) {
    free(ctx->colors);
    ctx->colors = NULL;

    RErrorCode = RERR_NOMEMORY;

    return False;
  }

#define calc(max, mult) (((i / stdcmap->mult) % (stdcmap->max + 1)) * 65535) / stdcmap->max

  for (i = 0; i < ctx->ncolors; i++) {
    ctx->colors[i].pixel = i + stdcmap->base_pixel;
    ctx->colors[i].red = calc(red_max, red_mult);
    ctx->colors[i].green = calc(green_max, green_mult);
    ctx->colors[i].blue = calc(blue_max, blue_mult);

    ctx->pixels[i] = ctx->colors[i].pixel;
  }

#undef calc

  return True;
}

static Bool setupStandardColormap(RContext *ctx, Atom property)
{
#ifdef HAVE_LIBXMU
  if (!XmuLookupStandardColormap(ctx->dpy, ctx->screen_number, ctx->visual->visualid, ctx->depth,
                                 property, True, True)) {
    RErrorCode = RERR_STDCMAPFAIL;

    return False;
  }
  return True;
#else
  (void)ctx;
  (void)property;
  RErrorCode = RERR_STDCMAPFAIL;
  return False;
#endif
}

static XColor *allocateColor(RContext *ctx, XColor *colors, int ncolors)
{
  XColor avcolors[256];
  int avncolors;
  int i, r, g, b;
  int retries;

  for (i = 0; i < ncolors; i++) {
#ifdef WRLIB_DEBUG
    fprintf(stderr, "trying:%x,%x,%x\n", colors[i].red, colors[i].green, colors[i].blue);
#endif
    if (!XAllocColor(ctx->dpy, ctx->cmap, &(colors[i]))) {
      colors[i].flags = 0; /* failed */
#ifdef WRLIB_DEBUG
      fprintf(stderr, "failed:%x,%x,%x\n", colors[i].red, colors[i].green, colors[i].blue);
#endif
    } else {
      colors[i].flags = DoRed | DoGreen | DoBlue;
#ifdef WRLIB_DEBUG
      fprintf(stderr, "success:%x,%x,%x\n", colors[i].red, colors[i].green, colors[i].blue);
#endif
    }
  }
  /* try to allocate close values for the colors that couldn't
   * be allocated before */
  avncolors = (1 << ctx->depth > 256 ? 256 : 1 << ctx->depth);
  for (i = 0; i < avncolors; i++)
    avcolors[i].pixel = i;

  XQueryColors(ctx->dpy, ctx->cmap, avcolors, avncolors);

  for (i = 0; i < ncolors; i++) {
    if (colors[i].flags == 0) {
      int j;
      unsigned long cdiff = 0xffffffff, diff;
      unsigned long closest = 0;

      retries = 2;

      while (retries--) {
        /* find closest color */
        for (j = 0; j < avncolors; j++) {
          r = (colors[i].red - avcolors[i].red) >> 8;
          g = (colors[i].green - avcolors[i].green) >> 8;
          b = (colors[i].blue - avcolors[i].blue) >> 8;
          diff = r * r + g * g + b * b;
          if (diff < cdiff) {
            cdiff = diff;
            closest = j;
          }
        }
        /* allocate closest color found */
#ifdef WRLIB_DEBUG
        fprintf(stderr, "best match:%x,%x,%x => %x,%x,%x\n", colors[i].red, colors[i].green,
                colors[i].blue, avcolors[closest].red, avcolors[closest].green,
                avcolors[closest].blue);
#endif
        colors[i].red = avcolors[closest].red;
        colors[i].green = avcolors[closest].green;
        colors[i].blue = avcolors[closest].blue;
        if (XAllocColor(ctx->dpy, ctx->cmap, &colors[i])) {
          colors[i].flags = DoRed | DoGreen | DoBlue;
          break; /* succeeded, don't need to retry */
        }
#ifdef WRLIB_DEBUG
        fputs("close color allocation failed. Retrying...\n", stderr);
#endif
      }
    }
  }
  return colors;
}

static Bool allocatePseudoColor(RContext *ctx)
{
  XColor *colors;
  int i, ncolors, r, g, b;
  int cpc = ctx->attribs->colors_per_channel;

  ncolors = cpc * cpc * cpc;

  if (ncolors > (1 << ctx->depth)) {
    /* reduce colormap size */
    cpc = ctx->attribs->colors_per_channel = 1 << ((int)ctx->depth / 3);
    ncolors = cpc * cpc * cpc;
  }

  assert(cpc >= 2 && ncolors <= (1 << ctx->depth));

  colors = malloc(sizeof(XColor) * ncolors);
  if (!colors) {
    RErrorCode = RERR_NOMEMORY;
    return False;
  }

  ctx->pixels = malloc(sizeof(unsigned long) * ncolors);
  if (!ctx->pixels) {
    free(colors);
    RErrorCode = RERR_NOMEMORY;
    return False;
  }

  i = 0;

  if ((ctx->attribs->flags & RC_GammaCorrection) && ctx->attribs->rgamma > 0 &&
      ctx->attribs->ggamma > 0 && ctx->attribs->bgamma > 0) {
    float rg, gg, bg;
    float tmp;

    /* do gamma correction */
    rg = 1.0F / ctx->attribs->rgamma;
    gg = 1.0F / ctx->attribs->ggamma;
    bg = 1.0F / ctx->attribs->bgamma;
    for (r = 0; r < cpc; r++) {
      for (g = 0; g < cpc; g++) {
        for (b = 0; b < cpc; b++) {
          colors[i].red = (r * 0xffff) / (cpc - 1);
          colors[i].green = (g * 0xffff) / (cpc - 1);
          colors[i].blue = (b * 0xffff) / (cpc - 1);
          colors[i].flags = DoRed | DoGreen | DoBlue;

          tmp = (float)colors[i].red / 65536.0F;
          colors[i].red = (unsigned short)(65536.0F * powf(tmp, rg));

          tmp = (float)colors[i].green / 65536.0F;
          colors[i].green = (unsigned short)(65536.0F * powf(tmp, gg));

          tmp = (float)colors[i].blue / 65536.0F;
          colors[i].blue = (unsigned short)(65536.0F * powf(tmp, bg));

          i++;
        }
      }
    }

  } else {
    for (r = 0; r < cpc; r++) {
      for (g = 0; g < cpc; g++) {
        for (b = 0; b < cpc; b++) {
          colors[i].red = (r * 0xffff) / (cpc - 1);
          colors[i].green = (g * 0xffff) / (cpc - 1);
          colors[i].blue = (b * 0xffff) / (cpc - 1);
          colors[i].flags = DoRed | DoGreen | DoBlue;
          i++;
        }
      }
    }
  }
  /* try to allocate the colors */
  ctx->colors = allocateColor(ctx, colors, ncolors);
  ctx->ncolors = ncolors;

  /* fill the pixels shortcut array */
  for (i = 0; i < ncolors; i++) {
    ctx->pixels[i] = ctx->colors[i].pixel;
  }

  return True;
}

static XColor *allocateGrayScale(RContext *ctx)
{
  XColor *colors;
  int i, ncolors;
  int cpc = ctx->attribs->colors_per_channel;

  ncolors = cpc * cpc * cpc;

  if (ctx->vclass == StaticGray) {
    /* we might as well use all grays */
    ncolors = 1 << ctx->depth;
  } else {
    if (ncolors > (1 << ctx->depth)) {
      /* reduce colormap size */
      cpc = ctx->attribs->colors_per_channel = 1 << ((int)ctx->depth / 3);
      ncolors = cpc * cpc * cpc;
    }

    assert(cpc >= 2 && ncolors <= (1 << ctx->depth));
  }

  if (ncolors >= 256 && ctx->vclass == StaticGray) {
    /* don't need dithering for 256 levels of gray in StaticGray visual */
    ctx->attribs->render_mode = RBestMatchRendering;
  }

  colors = malloc(sizeof(XColor) * ncolors);
  if (!colors) {
    RErrorCode = RERR_NOMEMORY;
    return False;
  }
  for (i = 0; i < ncolors; i++) {
    colors[i].red = (i * 0xffff) / (ncolors - 1);
    colors[i].green = (i * 0xffff) / (ncolors - 1);
    colors[i].blue = (i * 0xffff) / (ncolors - 1);
    colors[i].flags = DoRed | DoGreen | DoBlue;
  }

  /* try to allocate the colors */
  return allocateColor(ctx, colors, ncolors);
}

static Bool setupPseudoColorColormap(RContext *context)
{
  Atom property = 0;

  if (context->attribs->standard_colormap_mode == RCreateStdColormap) {
    property = XInternAtom(context->dpy, "RGB_DEFAULT_MAP", False);

    if (!setupStandardColormap(context, property)) {
      return False;
    }
  }

  if (context->attribs->standard_colormap_mode != RIgnoreStdColormap) {
    XStandardColormap *maps;
    int count, i;

    if (!property) {
      property = XInternAtom(context->dpy, "RGB_BEST_MAP", False);
      if (!XGetRGBColormaps(context->dpy, DefaultRootWindow(context->dpy), &maps, &count,
                            property)) {
        maps = NULL;
      }

      if (!maps) {
        property = XInternAtom(context->dpy, "RGB_DEFAULT_MAP", False);
        if (!XGetRGBColormaps(context->dpy, DefaultRootWindow(context->dpy), &maps, &count,
                              property)) {
          maps = NULL;
        }
      }
    } else {
      if (!XGetRGBColormaps(context->dpy, DefaultRootWindow(context->dpy), &maps, &count,
                            property)) {
        maps = NULL;
      }
    }

    if (maps) {
      int theMap = -1;

      for (i = 0; i < count; i++) {
        if (maps[i].visualid == context->visual->visualid) {
          theMap = i;
          break;
        }
      }

      if (theMap < 0) {
        fprintf(stderr, _("wrlib: no standard colormap found for visual 0x%lX\n"),
                context->visual->visualid);
      }

      if (theMap >= 0 && allocateStandardPseudoColor(context, &maps[theMap])) {
        context->std_rgb_map = XAllocStandardColormap();

        *context->std_rgb_map = maps[theMap];

        context->cmap = context->std_rgb_map->colormap;

        XFree(maps);

        return True;
      }

      XFree(maps);
    }
  }

  context->attribs->standard_colormap_mode = RIgnoreStdColormap;

  /* RIgnoreStdColormap and fallback */
  return allocatePseudoColor(context);
}

static char *mygetenv(const char *var, int scr)
{
  char *p;
  char varname[64];

  snprintf(varname, sizeof(varname), "%s%i", var, scr);
  p = getenv(varname);
  if (!p) {
    p = getenv(var);
  }
  return p;
}

static void gatherconfig(RContext *context, int screen_n)
{
  char *ptr;

  ptr = mygetenv("WRASTER_GAMMA", screen_n);
  if (ptr) {
    float g1, g2, g3;
    if (sscanf(ptr, "%f/%f/%f", &g1, &g2, &g3) != 3 || g1 <= 0.0F || g2 <= 0.0F || g3 <= 0.0F) {
      fprintf(stderr, _("wrlib: invalid value \"%s\" for %s\n"), ptr, "WRASTER_GAMMA");
    } else {
      context->attribs->flags |= RC_GammaCorrection;
      context->attribs->rgamma = g1;
      context->attribs->ggamma = g2;
      context->attribs->bgamma = g3;
    }
  }
  ptr = mygetenv("WRASTER_COLOR_RESOLUTION", screen_n);
  if (ptr) {
    int i;
    if (sscanf(ptr, "%d", &i) != 1 || i < 2 || i > 6) {
      fprintf(stderr, _("wrlib: invalid value \"%s\" for %s\n"), ptr, "WRASTER_COLOR_RESOLUTION");
    } else {
      context->attribs->flags |= RC_ColorsPerChannel;
      context->attribs->colors_per_channel = i;
    }
  }
}

static void getColormap(RContext *context, int screen_number)
{
  Colormap cmap = None;
  XStandardColormap *cmaps;
  int ncmaps, i;

  if (XGetRGBColormaps(context->dpy, RootWindow(context->dpy, screen_number), &cmaps, &ncmaps,
                       XA_RGB_DEFAULT_MAP)) {
    for (i = 0; i < ncmaps; ++i) {
      if (cmaps[i].visualid == context->visual->visualid) {
        cmap = cmaps[i].colormap;
        break;
      }
    }
    XFree(cmaps);
  }
  if (cmap == None) {
    XColor color;

    cmap = XCreateColormap(context->dpy, RootWindow(context->dpy, screen_number), context->visual,
                           AllocNone);

    color.red = color.green = color.blue = 0;
    XAllocColor(context->dpy, cmap, &color);
    context->black = color.pixel;

    color.red = color.green = color.blue = 0xffff;
    XAllocColor(context->dpy, cmap, &color);
    context->white = color.pixel;
  }
  context->cmap = cmap;
}

static int count_offset(unsigned long mask)
{
  int i;

  i = 0;
  while ((mask & 1) == 0) {
    i++;
    mask = mask >> 1;
  }
  return i;
}

RContext *RCreateContext(Display *dpy, int screen_number, const RContextAttributes *attribs)
{
  RContext *context;
  XGCValues gcv;

  context = malloc(sizeof(RContext));
  if (!context) {
    RErrorCode = RERR_NOMEMORY;
    return NULL;
  }
  memset(context, 0, sizeof(RContext));

  context->dpy = dpy;

  context->screen_number = screen_number;

  context->attribs = malloc(sizeof(RContextAttributes));
  if (!context->attribs) {
    free(context);
    RErrorCode = RERR_NOMEMORY;
    return NULL;
  }
  if (!attribs)
    *context->attribs = DEFAULT_CONTEXT_ATTRIBS;
  else
    *context->attribs = *attribs;

  if (!(context->attribs->flags & RC_StandardColormap)) {
    context->attribs->standard_colormap_mode = RUseStdColormap;
  }

  if (!(context->attribs->flags & RC_ScalingFilter)) {
    context->attribs->flags |= RC_ScalingFilter;
    context->attribs->scaling_filter = RMitchellFilter;
  }

  /* get configuration from environment variables */
  gatherconfig(context, screen_number);
  wraster_change_filter(context->attribs->scaling_filter);
  if ((context->attribs->flags & RC_VisualID)) {
    XVisualInfo *vinfo, templ;
    int nret;

    templ.screen = screen_number;
    templ.visualid = context->attribs->visualid;
    vinfo = XGetVisualInfo(context->dpy, VisualIDMask | VisualScreenMask, &templ, &nret);
    if (!vinfo || nret == 0) {
      free(context);
      RErrorCode = RERR_BADVISUALID;
      return NULL;
    }

    if (vinfo[0].visual == DefaultVisual(dpy, screen_number)) {
      context->attribs->flags |= RC_DefaultVisual;
    } else {
      XSetWindowAttributes attr;
      unsigned long mask;

      context->visual = vinfo[0].visual;
      context->depth = vinfo[0].depth;
      context->vclass = vinfo[0].class;
      getColormap(context, screen_number);
      attr.colormap = context->cmap;
      attr.override_redirect = True;
      attr.border_pixel = 0;
      attr.background_pixel = 0;
      mask = CWBorderPixel | CWColormap | CWOverrideRedirect | CWBackPixel;
      context->drawable =
          XCreateWindow(dpy, RootWindow(dpy, screen_number), 1, 1, 1, 1, 0, context->depth,
                        CopyFromParent, context->visual, mask, &attr);
    }
    XFree(vinfo);
  }

  /* use default */
  if (!context->visual) {
    if ((context->attribs->flags & RC_DefaultVisual) || !bestContext(dpy, screen_number, context)) {
      context->visual = DefaultVisual(dpy, screen_number);
      context->depth = DefaultDepth(dpy, screen_number);
      context->cmap = DefaultColormap(dpy, screen_number);
      context->drawable = RootWindow(dpy, screen_number);
      context->black = BlackPixel(dpy, screen_number);
      context->white = WhitePixel(dpy, screen_number);
      context->vclass = context->visual->class;
    }
  }

  gcv.function = GXcopy;
  gcv.graphics_exposures = False;
  context->copy_gc = XCreateGC(dpy, context->drawable, GCFunction | GCGraphicsExposures, &gcv);

  if (context->vclass == PseudoColor || context->vclass == StaticColor) {
    if (!setupPseudoColorColormap(context)) {
      free(context);
      return NULL;
    }
  } else if (context->vclass == GrayScale || context->vclass == StaticGray) {
    context->colors = allocateGrayScale(context);
    if (!context->colors) {
      free(context);
      return NULL;
    }
  } else if (context->vclass == TrueColor) {
    /* calc offsets to create a TrueColor pixel */
    context->red_offset = count_offset(context->visual->red_mask);
    context->green_offset = count_offset(context->visual->green_mask);
    context->blue_offset = count_offset(context->visual->blue_mask);
    /* disable dithering on 24 bits visuals */
    if (context->depth >= 24)
      context->attribs->render_mode = RBestMatchRendering;
  }

  /* check avaiability of MIT-SHM */
#ifdef USE_XSHM
  if (!(context->attribs->flags & RC_UseSharedMemory)) {
    context->attribs->flags |= RC_UseSharedMemory;
    context->attribs->use_shared_memory = True;
  }

  if (context->attribs->use_shared_memory) {
    int major, minor;
    Bool sharedPixmaps;

    context->flags.use_shared_pixmap = 0;

    if (!XShmQueryVersion(context->dpy, &major, &minor, &sharedPixmaps)) {
      context->attribs->use_shared_memory = False;
    } else {
      if (XShmPixmapFormat(context->dpy) == ZPixmap)
        context->flags.use_shared_pixmap = sharedPixmaps;
    }
  }
#endif

  return context;
}

void RDestroyContext(RContext *context)
{
  if (context) {
    if (context->copy_gc)
      XFreeGC(context->dpy, context->copy_gc);
    if (context->attribs) {
      if ((context->attribs->flags & RC_VisualID) && !(context->attribs->flags & RC_DefaultVisual))
        XDestroyWindow(context->dpy, context->drawable);
      free(context->attribs);
    }
    free(context);
  }
}

static Bool bestContext(Display *dpy, int screen_number, RContext *context)
{
  XVisualInfo *vinfo = NULL, rvinfo;
  int best = -1, numvis, i;
  long flags;
  XSetWindowAttributes attr;

  rvinfo.class = TrueColor;
  rvinfo.screen = screen_number;
  flags = VisualClassMask | VisualScreenMask;

  vinfo = XGetVisualInfo(dpy, flags, &rvinfo, &numvis);
  if (vinfo) { /* look for a TrueColor, 24-bit or more (pref 24) */
    for (i = numvis - 1, best = -1; i >= 0; i--) {
      if (vinfo[i].depth == 24)
        best = i;
      else if (vinfo[i].depth > 24 && best < 0)
        best = i;
    }
  }
  if (best > -1) {
    context->visual = vinfo[best].visual;
    context->depth = vinfo[best].depth;
    context->vclass = vinfo[best].class;
    getColormap(context, screen_number);
    attr.colormap = context->cmap;
    attr.override_redirect = True;
    attr.border_pixel = 0;
    context->drawable = XCreateWindow(dpy, RootWindow(dpy, screen_number), 1, 1, 1, 1, 0,
                                      context->depth, CopyFromParent, context->visual,
                                      CWBorderPixel | CWColormap | CWOverrideRedirect, &attr);
  }
  if (vinfo)
    XFree((char *)vinfo);

  if (best < 0)
    return False;
  else
    return True;
}
